package com.yancloud.android.contractclient;

import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.yancloud.android.contractclient.ActionExecutor;
import com.yancloud.android.contractclient.CMClientController;
import com.yancloud.android.contractclient.ResultCallback;

import crypto.SM2;
import crypto.SM2KeyPair;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
import io.netty.util.CharsetUtil;

@Sharable
public class CMClientHandler extends SimpleChannelInboundHandler<Object> {

	private final SM2KeyPair keyPair;
	private WebSocketClientHandshaker handshaker;
	private ChannelPromise handshakeFuture;
	private boolean isConnected;
	ChannelHandlerContext ctx;
	public CMClientController controller;
 	ActionExecutor<ResultCallback, JsonObject> ae;
	public static ExecutorService executorService = Executors.newFixedThreadPool(10);

	public CMClientHandler() {
		keyPair = new SM2().generateKeyPair();
		controller = new CMClientController(keyPair.getPublicKeyStr());
		controller.initHandler(this);
 		ae = new ActionExecutor<>(executorService, controller);
		isConnected = false;
	}

	public ChannelFuture handshakeFuture() {
		return handshakeFuture;
	}

	@Override
	public void handlerAdded(ChannelHandlerContext ctx) {
		handshakeFuture = ctx.newPromise();
	}

	@Override
	public void channelActive(ChannelHandlerContext ctx) {
		this.ctx = ctx;
 		Map<String, String> setNodeID = new HashMap<String, String>();
		setNodeID.put("action", "setNodeID");
		setNodeID.put("id", keyPair.getPublicKeyStr());
		SM2.Signature sig = new SM2().sign(keyPair.getPublicKeyStr().getBytes(),
				keyPair.getPrivateKey().toString(16));
		setNodeID.put("signature", sig.toString());
		setNodeID.put("nodeName","ContractManager@Phone");
		isConnected = (true);
		//controller.init(this);
		sendMsg(new Gson().toJson(setNodeID));
	}

	@Override
	public void channelInactive(ChannelHandlerContext ctx) {
		isConnected = (false);
	}

	public synchronized void sendMsg(String msg) {
 		System.out.println("[CMClientHandler] sendMsg, TID:" + Thread.currentThread().getId() + msg);
		ctx.channel().writeAndFlush(Unpooled.copiedBuffer(msg.getBytes()));
	}

	@Override
	public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
		Channel ch = ctx.channel();

		ByteBuf bb = (ByteBuf) msg;
		try {
			final JsonObject arg = new JsonParser().parse(new InputStreamReader(new ByteBufInputStream(bb)))
					.getAsJsonObject();
			System.out.println("[CMClientHandler] rec:" + arg.toString());
			if (arg.has("action")) {
				final String action = arg.get("action").getAsString();
				ae.handle(action, arg, new ResultCallback() {
					@Override
					public void onResult(String str) {
						sendMsg(str);
					}
				});
			}
		} catch (java.lang.IllegalArgumentException e) {
			System.out.println("[CMClientHandler] can't handle action:" + e.getMessage());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		if (!handshakeFuture.isDone()) {
			handshakeFuture.setFailure(cause);
		}
		ctx.close();
	}

	public boolean isConnected() {
		return ctx != null && ctx.channel() != null && ctx.channel().isOpen();
	}

	public void close() {
		try {
			ctx.close();
			isConnected = false;
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}